﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using System.Xml;
using System.Threading;
using System.Reflection;
using SPMS.Commons.Configuration;
using SPMS.Commons.Configuration.Xml;
using SPMS.Log.Utility;

namespace SPMS.Log.Repository
{
    /// <summary>
    /// 默认的日志存储器工厂
    /// </summary>
    /// <remarks>提供了日志存储器工厂的默认实现</remarks>
    /// <history>
    /// [zanezeng]               2009/10/14 11:23    创建
    /// </history>
    public sealed class DefaultRepositoryFactory : IRepositoryFactory
    {
        #region ---Constructor

        /// <summary>
        /// 初始化类型<see cref="DefaultRepositoryFactory"/>的新实例
        /// </summary>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/6 10:54    创建
        /// </history>
        public DefaultRepositoryFactory()
        {
            //创建日志创建委托
            this.m_CreatingHandler = new CreateRepositoryHandler( CreateRepository );
        }

        #endregion

        #region ---IRepositoryFactory

        /// <summary>
        /// 获得一个值，该值用于判断当前日志存储器工厂是否已经关闭
        /// </summary>
        /// <value>如果当前日志存储器工厂已经被关闭，则返回<c>true</c>，否则返回<c>false</c>。</value>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/6 11:27    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/6 11:30    创建
        /// </history>
        public bool IsClosed
        {
            get
            {
                return this.m_IsDisposed;
            }
        }

        /// <summary>
        /// 创建日志存储器
        /// </summary>
        /// <returns>创建的日志存储器</returns>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/13 15:39    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/14 11:24    创建
        /// </history>
        public ILoggingRepository GetRepository()
        {
            //断言没有被处置
            this.AssertNotDisposed();

            //转调
            return this.GetRepository( string.Empty );
        }

        /// <summary>
        /// 根据给定的配置信息创建日志存储器
        /// </summary>
        /// <param name="configuration">用于创建日志存储器的配置信息</param>
        /// <returns>创建的日志存储器</returns>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/14 10:43    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/14 11:25    创建
        /// </history>
        public ILoggingRepository GetRepository( object configuration )
        {
            //断言没有被处置
            this.AssertNotDisposed();

            //解析配置文件名称
            string configFileName = ResolveConfigFileName( configuration );

            //用于保存日志存储器
            ILoggingRepository repository = null;

            //尝试获得日志存储器
            if (!this.TryGetRepositoryFromCache( configFileName, out repository ))
            {
                //同步事件
                ManualResetEvent syncEvent = null;

                //同步
                lock (this.m_CreatingRepository)
                {
                    //判断日志存储器是否正在创建
                    if (!this.m_CreatingRepository.TryGetValue( configFileName, out syncEvent ))
                    {
                        //如果没有处于创建状态，则尝试获得缓存的日志存储器
                        if (this.TryGetRepositoryFromCache( configFileName, out repository ))
                        {
                            //返回获得日志存储器
                            return repository;
                        }
                        //如果缓存的日志存储器不存在，则创建日志存储器
                        else
                        {
                            //创建同步事件 
                            syncEvent = new ManualResetEvent( false );

                            //记录将要创建的日志存储器
                            this.m_CreatingRepository.Add( configFileName, syncEvent );

                            //异步创建日志存储器
                            this.AsyncCreateRepository( configFileName, syncEvent );
                        }
                    }
                }

                //判断是否需要等待创建
                if (null != syncEvent && !syncEvent.SafeWaitHandle.IsClosed)
                {
#if DEBUG
                    //等待创建日志存储器（调试版不超时）
                    syncEvent.WaitOne();
#else
                    //等待创建日志存储器
                    if (!syncEvent.WaitOne( CREATEREPOSITORY_TIMEOUT ))
                    {
                        //记录日志
                        if (LoggingContext.MetaLog.IsErrorEnabled)
                        {
                            LoggingContext.MetaLog.Error( "通过配置文件'{0}'创建日志存储器超时。超时时间为'{1}'毫秒", configFileName, CREATEREPOSITORY_TIMEOUT );
                        }

                        //清除正在创建日志存储器
                        lock (this.m_CreatingRepository)
                        {
                            this.m_CreatingRepository.Remove( configFileName );
                        }

                        //抛出异常
                        throw new TimeoutException( string.Format( Properties.Resources.MSG_CREATEREPOSITORY_TIMEOUT, configFileName ) );
                    }
#endif
                }

                //尝试获得日志存储器
                this.TryGetRepositoryFromCache( configFileName, out repository );
            }

            //返回存储器
            return repository;
        }

        /// <summary>
        /// 关闭当前日志存储器工厂
        /// </summary>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/6 11:29    创建
        /// </history>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/6 11:30    创建
        /// </history>
        public void Close()
        {
            //处置日志存储器工厂
            this.Dispose();
        }

        #endregion

        #region ---IDisposable

        /// <summary>
        /// 执行与释放或重置非托管资源相关的应用程序定义的任务
        /// </summary>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2010/1/6 10:51    创建
        /// </history>
        public void Dispose()
        {
            //判断是否已经被处置
            if (this.m_IsDisposed)
            {
                //如果已经被处置，则直接返回
                return;
            }

            //设置已经被处置
            this.m_IsDisposed = true;

            //获得所有的日志存储器
            ILoggingRepository[] repositories = this.m_SyncRepositories.Values.ToArray();

            //清除所有的日志存储器
            this.m_SyncRepositories.Clear();

            //获得所有的日志存储器
            foreach (ILoggingRepository repository in repositories)
            {
                //处置日志存储器
                repository.Dispose();
            }
        }

        #endregion

        #region ---Private Method

        /// <summary>
        /// 尝试从日志存储器缓存中获得给定配置文件的日志存储器
        /// </summary>
        /// <param name="configFileName">用于获得日志存储器的配置文件名称</param>
        /// <param name="repository">用于返回缓存的日志存储器</param>
        /// <returns>
        /// 	<c>true</c>：存在缓存<br/>
        /// 	<c>false</c>：不存在缓存
        /// </returns>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/14 16:29    创建
        /// </history>
        private bool TryGetRepositoryFromCache( string configFileName, out ILoggingRepository repository )
        {
            //尝试获得日志存储器
            if (this.m_SyncRepositories.TryGetValue( configFileName, out repository ))
            {
                //返回日志存储器是否有效
                return (null != repository);
            }

            //返回失败
            return false;
        }

        /// <summary>
        /// 从给定的配置对象中解析配置文件名称
        /// </summary>
        /// <param name="configuration">用于解析配置文件名称的配置对象</param>
        /// <returns>从给定的配置对象中解析出来的配置文件名称</returns>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/14 16:27    创建
        /// </history>
        private static string ResolveConfigFileName( object configuration )
        {
            //将配置信息转化为配置文件路径
            string configFileName = (string)configuration;

            //判断配置文件名称是否为空
            if (string.IsNullOrEmpty( configFileName ))
            {
                //如果配置文件名称为空，则默认为应用配置文件
                configFileName = AppDomain.CurrentDomain.SetupInformation.ConfigurationFile;
            }

            //判断配置文件是否为相对路径
            if (!Path.IsPathRooted( configFileName ))
            {
                //如果是相对路径，则默认为相对于应用程序域的基路径
                configFileName = Path.Combine( AppDomain.CurrentDomain.BaseDirectory, configFileName );
            }

            //返回配置文件名称
            return configFileName;
        }

        /// <summary>
        /// 异步创建日志存储器
        /// </summary>
        /// <param name="configFileName">用于获得日志存储器的配置文件名称</param>
        /// <param name="creatingEvent">创建同步事件</param>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/14 16:49    创建
        /// </history>
        private void AsyncCreateRepository( string configFileName, ManualResetEvent creatingEvent )
        {
            try
            {
                //异步调用
                this.m_CreatingHandler.BeginInvoke( configFileName, creatingEvent, null, null );
            }
            catch (Exception ex)
            {
                //输出日志
                if (LoggingContext.MetaLog.IsErrorEnabled)
                {
                    LoggingContext.MetaLog.Error( ex, "通过配置文件'{0}'创建日志存储器的过程中发生错误，无法启动异步委托", configFileName );
                }

                //设置事件状态
                if (null != creatingEvent && !creatingEvent.SafeWaitHandle.IsClosed)
                {
                    creatingEvent.Set();
                }

                //删除正在创建事件
                this.m_CreatingRepository.Remove( configFileName );

                //抛出异常
                throw;
            }
        }

        /// <summary>
        /// 创建日志存储器
        /// </summary>
        /// <param name="configFileName">用于获得日志存储器的配置文件名称</param>
        /// <param name="creatingEvent">创建同步事件</param>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/14 16:54    创建
        /// </history>
        private void CreateRepository( string configFileName, ManualResetEvent creatingEvent )
        {
            //创建日志存储器
            LoggingRepository repository = new LoggingRepository( configFileName );

            //将创建的日志存储器添加到日志存储器缓存中
            this.m_SyncRepositories.Add( configFileName, repository );

            try
            {
                //判断日志文件是否存在
                if (File.Exists( configFileName ))
                {
                    //读取配置文件内容
                    IConfiguration configuration = this.LoadConfiguration( configFileName );

                    //判断配置是否有效
                    if (null != configuration)
                    {
                        //配置存储器
                        repository.Configure( configuration );
                    }
                }
            }
            catch (Exception ex)
            {
                //输出日志
                if (LoggingContext.MetaLog.IsErrorEnabled)
                {
                    LoggingContext.MetaLog.Error( ex, "调用日志存储器工厂'{0}'通过配置文件'{1}'创建日志存储器发生错误", this, configFileName );
                }
            }
            finally
            {
                //通知事件
                if (null != creatingEvent && !creatingEvent.SafeWaitHandle.IsClosed)
                {
                    creatingEvent.Set();
                }

                //删除正在创建事件
                lock (this.m_CreatingRepository)
                {
                    this.m_CreatingRepository.Remove( configFileName );
                }
            }
        }

        /// <summary>
        /// 从给定的配置文件中加载配置
        /// </summary>
        /// <param name="configFileName">要加载的配置文件名称</param>
        /// <returns>从给定的配置文件中读取的配置信息</returns>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]               2009/10/14 17:03    创建
        /// </history>
        private IConfiguration LoadConfiguration( string configFileName )
        {
            //用于保存配置对象
            XmlConfiguration configuration = null;

            //判断是否加载成功
            if (null == configuration)
            {
                //创建XML文档对象
                XmlDocument document = new XmlDocument();

                //读取配置文件
                document.Load( configFileName );

                //获得日志配置节点
                XmlNodeList nodeList = document.GetElementsByTagName( SECTION_NAME );

                //判断是否有配置节点
                if (nodeList.Count > 0)
                {
                    //创建配置对象
                    configuration = new XmlConfiguration();

                    //加载配置信息
                    configuration.Load( nodeList[0] );
                }
            }

            //返回配置对象
            return configuration;
        }

        /// <summary>
        /// 断言对象没有被处置
        /// </summary>
        /// <remarks></remarks>
        /// <history>
        /// [zanezeng]   2008-12-7 19:42    Created
        /// </history>
        private void AssertNotDisposed()
        {
            //判断是否已经被处置
            if (this.m_IsDisposed)
            {
                //抛出异常
                throw new ObjectDisposedException( Properties.Resources.MSG_REPOSITORYFACTORY_CLOSED );
            }
        }

        #endregion

        #region ---Field

        /// <summary>
        /// 用于记录日志存储器工厂是否已经被处置
        /// </summary>
        private bool m_IsDisposed;

        /// <summary>
        /// 日志存储器创建委托
        /// </summary>
        private CreateRepositoryHandler m_CreatingHandler;

        /// <summary>
        /// 日志存储器创建委托
        /// </summary>
        private delegate void CreateRepositoryHandler( string configFileName, ManualResetEvent creatingEvent );

        /// <summary>
        /// 正在创建的日志存储器
        /// </summary>
        private Dictionary<string, ManualResetEvent> m_CreatingRepository = new Dictionary<string, ManualResetEvent>( StringComparer.InvariantCultureIgnoreCase );

        /// <summary>
        /// 日志存储器缓存中
        /// </summary>
        private SynchronizedDictionary<string, ILoggingRepository> m_SyncRepositories = new SynchronizedDictionary<string, ILoggingRepository>( StringComparer.InvariantCultureIgnoreCase );

        /// <summary>
        /// 配置节点名称
        /// </summary>
        public const string SECTION_NAME = "leyser-log";

        /// <summary>
        /// 创建日志存储器超时，默认为10秒
        /// </summary>
        private const int CREATEREPOSITORY_TIMEOUT = 10 * 1000;

        #endregion
    }
}
